package com.enotary.flutter_obs;

import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.CompleteMultipartUploadResult;
import com.obs.services.model.UploadFileRequest;

import java.io.File;
import java.util.HashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


/**
 * 华为obs 上传文件
 */
public class OBSManager {

    private ObsClient obsClient;
    private HashMap<String, Object> data;
    private ScheduledExecutorService service;
    private String filePathCheckPoint;

    /**
     * 开始网络请求
     */
    public static OBSManager getInstance(HashMap<String, Object> response,String path, EventCallback callback) {
        OBSManager manager = new OBSManager();
        manager.setFilePathCheckPoint(path);
        manager.init(response, callback);
        return manager;
    }

    private void init(HashMap<String, Object> response, EventCallback callback) {
        if(ObsUtils.isMainThread()) {
            if (service == null) {
                service = Executors.newSingleThreadScheduledExecutor();
            }
            service.execute(() -> uploadObs(response, callback, 0));
        } else {
            uploadObs(response, callback, 0);
        }
    }

    public void setFilePathCheckPoint(String path){
        this.filePathCheckPoint = path;
    }

    public String getCancelKey(){
        return data == null ? null : (String)data.get("filePath");
    }

    /**
     * 华为obs 使用临时url上传文件
     * <a href="https://support.huaweicloud.com/sdk-android-devg-obs/obs_26_0701.html">使用临时url上传文件</a>
     * 断点续传
     * <a href="https://support.huaweicloud.com/sdk-android-devg-obs/obs_26_0411.html">断点续传</a>
     */
    private void uploadObs(HashMap<String, Object> response, EventCallback callback, int errorCount) {
        data = response;
        boolean retry = false;
        String requestId = "";
        try {
            String urlString = (String) response.get("endpoint");
            String accessKey = (String) response.get("accessKey");
            String accessSecret = (String) response.get("accessSecret");
            String securityToken = (String) response.get("securityToken");
            String bucketName = (String) response.get("bucketName");
            String objectKey = (String) response.get("objectKey");
            String filePath = (String) response.get("filePath");

            if (urlString != null && !urlString.startsWith("http")) {
                urlString = "https://" + urlString;
            }
            obsClient = new ObsClient(accessKey, accessSecret, securityToken, urlString);
            UploadFileRequest request = new UploadFileRequest(bucketName, objectKey, filePath);
            // 设置分段上传时的最大并发数,设置太大 会影响其他接口的使用
            request.setTaskNum(5);
            // 设置分段大小为5MB
            request.setPartSize(5 * 1024 * 1024);
            // 开启断点续传模式
            request.setEnableCheckpoint(true);
            // 开启断点续传存储文件,防止 外部路径 无法读写权限
            String tag = filePath == null ? "": filePath.replaceAll(File.separator,"");
            if (tag.length() > 150) tag = tag.substring(tag.length() - 150);
            String path = filePathCheckPoint == null ? filePath : filePathCheckPoint + File.separator + tag;
            request.setCheckpointFile(path + ".checkpoint.txt");
            request.setProgressListener(status -> {
                HashMap<String, Object> map = new HashMap<>(response);
                map.put("progress", status.getTransferPercentage());
                map.put("status", 1);
                map.put("#@type@#", "obs");
                callback.callback(map);
            });

            // 进行断点续传上传
            CompleteMultipartUploadResult result = obsClient.uploadFile(request);
            requestId = result.getRequestId();
            if(200 == result.getStatusCode()) {
                ObsUtils.logI("obs upload file finish, " + objectKey + "，" + filePath + "，" + "result" + result);
                HashMap<String, Object> map = new HashMap<>(response);
                map.put("status", 2);
                map.put("#@type@#", "obs");
                map.put("requestId", requestId);
                callback.callback(map);
            } else {
                if(errorCount > 9) {
                    HashMap<String, Object> map = new HashMap<>(response);
                    map.put("#@type@#", "obs");
                    map.put("status", 3);
                    map.put("requestId", result.getRequestId());
                    map.put("errorCode", result.getStatusCode());
                    map.put("errorMessage", result.toString());
                    callback.callback(map);
                } else {
                    retry = true;
                }
            }
        } catch (Exception e) {
            // 发生异常时可再次调用断点续传上传接口进行重新上传
            ObsUtils.logI("obs retry: " + errorCount + ", Error: " + e);
            if(errorCount > 9) {
                //使用新map，防止eventSink.success传递map时，代码这里改动map导致抛异常 java.util.ConcurrentModificationException
                HashMap<String, Object> map = new HashMap<>(response);
                map.put("#@type@#", "obs");
                map.put("status", 3);
                map.put("requestId", requestId);
                if(e instanceof ObsException) {
                    map.put("errorCode", ((ObsException) e).getErrorCode());
                    map.put("errorMessage", ((ObsException) e).getErrorMessage());
                } else {
                    map.put("errorCode", "11119");
                    map.put("errorMessage", e.getMessage());
                }
                map.put("error", e.toString());
                callback.callback(map);
            } else {
                retry = true;
            }
        } finally {
            cancel();
        }
        if (retry) {
            service.schedule(() ->
                            uploadObs(response, callback, errorCount + 1),
                    500, TimeUnit.MILLISECONDS);
        }
    }

    public boolean isCancel(){
        //单纯靠obsClient为空判断不够，如果创建Manager对象，因线程才会创建client对象，这时马上调用该方法 返回都是true
        return obsClient == null && data == null;
    }

    public void cancel() {
        ObsUtils.logI("obs cancel，client is null "+(obsClient == null));
        ObsUtils.close(obsClient);
        obsClient = null;
        data = null;
    }

    public interface EventCallback {
        void callback(HashMap<String, Object> data);
    }
}
